//go:build ignore

// ChainID Auto-Generation Tool
//
// This file generates the ChainID methods for the VAA struct in the SDK.
// It automatically creates String(), ChainIDFromString(), and GetAllNetworkIDs()
// methods by parsing ChainID constants from vaa/structs.go.
//
// ADDING A NEW CHAINID:
//  1. Add the new ChainID constant to vaa/structs.go following the pattern:
//     // ChainIDNewChain is the ChainID of NewChain
//     ChainIDNewChain ChainID = 99
//
// 2. Run `make go-generate` to regenerate the methods in vaa/chainid_generated.go
//
// 3. The generator will automatically:
//   - Extract the chain name from the constant (removes "ChainID" prefix)
//   - Convert to lowercase for string representation
//   - Handle special cases like testnet names (e.g., "PolygonSepolia" -> "polygon_sepolia")
//   - Add the new chain to all generated methods
//
// The generated file should never be edited manually - always regenerate it.
package main

import (
	"fmt"
	"go/ast"
	"go/parser"
	"go/token"
	"log"
	"os"
	"sort"
	"strconv"
	"strings"
	"text/template"
)

type ChainInfo struct {
	Name   string
	Value  int
	GoName string
}

func main() {
	// Parse the source file to extract ChainID constants from vaa/structs.go
	// This reads the Go source code and builds an Abstract Syntax Tree (AST)
	// to programmatically find all ChainID constant declarations
	fset := token.NewFileSet()
	node, err := parser.ParseFile(fset, "vaa/structs.go", nil, parser.ParseComments)
	if err != nil {
		log.Fatal(err)
	}

	// Stores each ChainID declared as a constant in vaa/structs.go
	// Each ChainInfo contains the chain name, numeric value, and Go constant name
	var chains []ChainInfo

	// Walk the AST to find ChainID constants
	// This traverses the parsed Go code looking for constant declarations
	// that have the type "ChainID"
	ast.Inspect(node, func(n ast.Node) bool {
		switch x := n.(type) {
		case *ast.GenDecl:
			if x.Tok == token.CONST {
				for _, spec := range x.Specs {
					if vspec, ok := spec.(*ast.ValueSpec); ok {
						// Check if this is a ChainID constant by examining the type
						if vspec.Type != nil {
							if ident, ok := vspec.Type.(*ast.Ident); ok && ident.Name == "ChainID" {
								for i, name := range vspec.Names {
									if name.Name == "ChainIDUnset" {
										// Skip the unset value (ChainIDUnset = 0)
										// This is a special case that shouldn't be included in generated methods
										continue
									}

									// Extract the numeric value from the constant declaration
									var value int
									if len(vspec.Values) > i {
										if basic, ok := vspec.Values[i].(*ast.BasicLit); ok {
											if v, err := strconv.Atoi(basic.Value); err == nil {
												value = v
											}
										}
									}

									// Extract the chain name from the constant name
									// e.g., "ChainIDEthereum" -> "Ethereum"
									chainName := strings.TrimPrefix(name.Name, "ChainID")

									// Convert to lowercase for string representation
									// e.g., "Ethereum" -> "ethereum"
									chainNameLower := strings.ToLower(chainName)

									// Handle special naming for testnet chains
									// Separate alt-EVM testnet names with underscores.
									// e.g. `PolygonSepolia` --> `polygon_sepolia`.
									// (Don't match on "sepolia" itself though.)
									if strings.HasSuffix(chainNameLower, "sepolia") && len(chainNameLower) > len("sepolia") {
										chainNameLower = fmt.Sprintf("%s_sepolia", strings.TrimSuffix(chainNameLower, "sepolia"))
									}

									// Store the chain information for code generation
									chains = append(chains, ChainInfo{
										Name:   chainNameLower, // String representation (e.g., "ethereum")
										Value:  value,          // Numeric ID (e.g., 2)
										GoName: name.Name,      // Go constant name (e.g., "ChainIDEthereum")
									})
								}
							}
						}
					}
				}
			}
		}
		return true
	})

	// Sort by numeric value for consistent output
	// This ensures the generated code has chains in the same order as the constants
	sort.Slice(chains, func(i, j int) bool {
		return chains[i].Value < chains[j].Value
	})

	// Generate the code template for the output file
	// This template creates three methods:
	// 1. String() - converts ChainID to string representation
	// 2. ChainIDFromString() - converts string to ChainID
	// 3. GetAllNetworkIDs() - returns slice of all known ChainIDs
	// NOTE: This should follow gofumpt formatting in order to pass CI checks.
	tmpl := `// Code generated by go generate; DO NOT EDIT.

package vaa

import (
	"fmt"
	"strings"
)

// String returns the string representation of the ChainID
func (c ChainID) String() string {
	switch c {
	case ChainIDUnset:
		return "unset"
{{- range .Chains }}
	case {{ .GoName }}:
		return "{{ .Name }}"
{{- end }}
	default:
		return fmt.Sprintf("unknown chain ID: %d", c)
	}
}

// ChainIDFromString converts from a chain's full name to its corresponding ChainID.
func ChainIDFromString(s string) (ChainID, error) {
	s = strings.ToLower(s)

	switch s {
{{- range .Chains }}
	case "{{ .Name }}":
		return {{ .GoName }}, nil
{{- end }}
	default:
		return ChainIDUnset, fmt.Errorf("unknown chain ID: %s", s)
	}
}

// GetAllNetworkIDs returns all known ChainIDs
func GetAllNetworkIDs() []ChainID {
	return []ChainID{
{{- range .Chains }}
		{{ .GoName }},
{{- end }}
	}
}
`

	// Write the generated code to the output file
	outfile := "vaa/chainid_generated.go"
	t, err := template.New("chainid").Parse(tmpl)
	if err != nil {
		log.Fatal(err)
	}

	output, err := os.Create(outfile)
	if err != nil {
		log.Fatal(err)
	}
	defer output.Close()

	// Execute the template with the extracted chain information
	err = t.Execute(output, struct {
		Chains []ChainInfo
	}{
		Chains: chains,
	})
	if err != nil {
		log.Fatal(err)
	}

	fmt.Printf("Generated %s with %d chains\n", outfile, len(chains))
}
